package com.handsome.mybatis.hot;

import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.FileSystems;
import java.nio.file.Paths;
import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
 * mybatis xml 热更新
 */
public class MapperChangeListener implements InitializingBean, ApplicationContextAware {
    private volatile ConfigurableApplicationContext context = null;
    private volatile Scanner scanner = null;

    /**
     * mapper 目录
     */
    private String mapperDir = "D:\\WebProject\\java\\handsome\\handsome-admin\\src\\main\\resources\\mapper";

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context = (ConfigurableApplicationContext) applicationContext;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        scanner = new Scanner();
        //新开辟一个线程监控mapper文件是否修改，新开辟线程是为了防止 watcher.take() 阻塞程序启动
        new Thread(new watchMapper()).start();
    }

    class Scanner {

        /**
         * 扫描对应的xml文件
         */
        private static final String COMMENTARIES_RESOURCE_PATTERN = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + "mapper/*.xml";
        private final ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

        Scanner() {
        }

        /**
         * 重新加载mapper
         *
         * @throws Exception 重新加载的异常
         */
        void reloadXML() throws Exception {
            SqlSessionFactory factory = context.getBean(SqlSessionFactory.class);
            Configuration configuration = factory.getConfiguration();
            removeConfig(configuration);
            for (Resource resource : findResource()) {
                try {
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resource.getInputStream(), configuration, resource.toString(), configuration.getSqlFragments());
                    xmlMapperBuilder.parse();
                } finally {
                    ErrorContext.instance().reset();
                }
            }

            for (Resource resource : findCommonEntityMapper()) {
                try {
                    XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resource.getInputStream(), configuration, resource.toString(), configuration.getSqlFragments());
                    try {
                        xmlMapperBuilder.parse();

                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                } finally {
                    ErrorContext.instance().reset();
                }
            }
        }

        /**
         * 移除Configuration中的mapper信息(mybatis的所有mapper信息保存在Configuration的字段中)
         *
         * @param configuration 配置
         * @throws Exception 异常
         */
        private void removeConfig(Configuration configuration) throws Exception {
            Class<?> classConfig = configuration.getClass();
            clearMap(classConfig, configuration, "mappedStatements");
            clearMap(classConfig, configuration, "caches");
            clearMap(classConfig, configuration, "resultMaps");
            clearMap(classConfig, configuration, "parameterMaps");
            clearMap(classConfig, configuration, "keyGenerators");
            clearMap(classConfig, configuration, "sqlFragments");
            clearSet(classConfig, configuration);
        }

        private void clearMap(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
            Field field = classConfig.getDeclaredField(fieldName);
            field.setAccessible(true);
            ((Map) field.get(configuration)).clear();
        }

        private void clearSet(Class<?> classConfig, Configuration configuration) throws Exception {
            Field field = classConfig.getDeclaredField("loadedResources");
            field.setAccessible(true);
            ((Set) field.get(configuration)).clear();
        }

        /**
         * 获取mapper文件
         *
         * @return FileSystemResource
         * @throws IOException 文件异常
         */
        private Resource[] findResource() throws IOException {
            File f = new File(mapperDir);
            File[] tempList = f.listFiles();

            FileSystemResource[] arraySource = tempList != null ? new FileSystemResource[tempList.length] : new FileSystemResource[0];
            for (int i = 0; i < (tempList != null ? tempList.length : 0); i++) {
                arraySource[i] = new FileSystemResource(tempList[i]);
            }
            return arraySource;
        }

        /**
         * 获取框架CommonEntityMapper
         *
         * @return Resource
         * @throws IOException 文件异常
         */
        private Resource[] findCommonEntityMapper() throws IOException {
            return resourcePatternResolver.getResources(COMMENTARIES_RESOURCE_PATTERN);
        }
    }

    /**
     * 监听mapper修改
     */
    class watchMapper implements Runnable {
        @Override
        public void run() {
            WatchService watcher = null;
            try {
                //注册服务
                watcher = FileSystems.getDefault().newWatchService();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                //只监听修改
                Paths.get(mapperDir).register(watcher,
                        //                StandardWatchEventKinds.ENTRY_CREATE,
                        //                StandardWatchEventKinds.ENTRY_DELETE,
                        StandardWatchEventKinds.ENTRY_MODIFY);
            } catch (IOException e) {
                e.printStackTrace();
            }

            while (true) {
                WatchKey key = null;
                try {
                    key = watcher != null ? watcher.take() : null;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //如果监控到mapper文件修改事件 poll出事件
                for (WatchEvent<?> event : Objects.requireNonNull(key != null ? key.pollEvents() : null)) {
                    System.out.println(event.context() + " comes to " + event.kind());
                    if (event.context().toString().endsWith(".xml")) {
                        System.out.println("reload mapper");
                        try {
                            //有mapper文件修改，重新加载mapper文件
                            scanner.reloadXML();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
                boolean valid = key.reset();
                if (!valid) {
                    break;
                }
            }
        }
    }
}
